+2005-05-04 Owen Taylor <otaylor@redhat.com>
+
+ * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
+ in focus tracking when we move between has_pointer_focus and
+ has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
+ and others)
+
+ * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
+ that could happen in the case of no window manager + keyboard grabs,
+ by moving to a more consistent model of when we pay attention
+ to mode=NotifyGrab/NotifyUngrab events.
+
+ * docs/focus_tracking.txt: Extensive writeup about how to track
+ focus under X11.
+
Wed May 4 13:21:41 2005 Søren Sandmann <sandmann@redhat.com>
* tests/testcairo.c (draw): Replace cairo_show_surface() uses with
+2005-05-04 Owen Taylor <otaylor@redhat.com>
+
+ * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
+ in focus tracking when we move between has_pointer_focus and
+ has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
+ and others)
+
+ * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
+ that could happen in the case of no window manager + keyboard grabs,
+ by moving to a more consistent model of when we pay attention
+ to mode=NotifyGrab/NotifyUngrab events.
+
+ * docs/focus_tracking.txt: Extensive writeup about how to track
+ focus under X11.
+
Wed May 4 13:21:41 2005 Søren Sandmann <sandmann@redhat.com>
* tests/testcairo.c (draw): Replace cairo_show_surface() uses with
+2005-05-04 Owen Taylor <otaylor@redhat.com>
+
+ * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
+ in focus tracking when we move between has_pointer_focus and
+ has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
+ and others)
+
+ * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
+ that could happen in the case of no window manager + keyboard grabs,
+ by moving to a more consistent model of when we pay attention
+ to mode=NotifyGrab/NotifyUngrab events.
+
+ * docs/focus_tracking.txt: Extensive writeup about how to track
+ focus under X11.
+
Wed May 4 13:21:41 2005 Søren Sandmann <sandmann@redhat.com>
* tests/testcairo.c (draw): Replace cairo_show_surface() uses with
--- /dev/null
+Notational conventions
+======================
+
+We have a window W that we are tracking events on. Focus
+can be on the following classes of objects
+
+ None : defined by X protocol
+ PointerRoot : defined by X protocol
+ W : the window itself
+ Ancestor : An ancestor of W, including W's root window
+ Descendant : A descendant of W
+ Other: : A window that is neither an ancestor or
+ descendant of W
+
+has_pointer(W): the pointer is in W or one of its descendants.
+
+NotifyPointer events
+====================
+
+X sends FocusIn or FocusOut events to W with a detail of NotifyPointer
+in the following transitions, when the pointer is inside W
+
+ Other => Ancestor: FocusIn
+ Ancestor => {Other,None}: FocusOut
+ Ancestor => PointerRoot: FocusOut, then FocusIn
+ {None,W,Descendant,Other} => PointerRoot: FocusIn
+ PointerRoot => Ancestor: FocusOut, then FocusIn
+ PointerRoot => {None,W,Descendant,Other} => FocusOut
+
+[ Ignoring keyboard grabs for the moment ]
+
+Basic focus tracking algorithm
+==============================
+
+Keystroke events are delivered within W if and only if one of two
+predicates hold:
+
+ has_focus_window(W): F==W || F==Descendant
+ has_pointer_focus(W): (F==Ancestor || F==PointerRoot) && has_pointer(W)
+
+These two conditions are mutually exclusive.
+
+has_focus_window(W) is easy to track.
+
+ FocusIn: detail != NotifyInferior: Set has_focus_iwndow
+ FocusOut: detail != NotifyInferior: Clear has_focus_iwndow
+
+has_pointer_focus(W) is harder to track.
+
+We can separate out the transitions from !has_pointer_focus(W) to
+has_pointer_focus(W) into four cases:
+
+ T1: [(F==W || F==Descendant) => F==Ancestor]; has_pointer(W)
+
+ T2: [(F==W || F==Descendant) => F==PointerRoot]; has_pointer(W)
+
+ T3: [(F==None || F==Other) => (F==PointerRoot || F==Ancestor)];
+ has_pointer(W)
+
+ T4: [!has_pointer(W) => has_pointer(W)]; (F==Ancestor || F==PointerRoot)
+
+All of these can be tracked by watching events on W.
+
+T1:, we get a FocusOut with a mode of Ancestor or Virtual
+ We need to separately track has_pointer(W) to distinguish
+ this from the case where we get these events and !has_pointer(W)
+
+T2, T3: together these are exactly the cases where we get
+ FocusIn/NotifyPointer.
+
+For T4, we get an EnterNotify with the focus flag set. An
+ EnterNotify with a focus flag set will also be sent if
+ F==W, so we have to to explicitly test for that case
+ using has_focus_window(W)
+
+
+The transitions from has_pointer_focus(W) to !has_pointer_focus(W)
+are exactly the opposite
+
+ F1: [(F==W || F==Descendant) <= F==Ancestor]; has_pointer(W)
+
+ F2: [(F==W || F==Descendant) <= F==PointerRoot]; has_pointer(W)
+
+ F3: [(F==None || F==Other) <= (F==PointerRoot || F==Ancestor)];
+ has_pointer(W)
+
+ F4: [!has_pointer(W) <= has_pointer(W)]; (F==Ancestor || F==PointerRoot)
+
+And can be tracked in the same ways:
+
+F1: we get a FocusIn with a mode of Ancestor or Virtual
+ We need to separately track has_pointer(W) to distinguish
+ this from the case we get these events and !has_pointer(W)
+
+F2, F3: together these are exactly the cases where we get
+ FocusOut/NotifyPointer.
+
+F4: we get an LeaveNotify with the focus flag set. An
+ LeaveNotify with a focus flag set will also be sent if
+ F==W, so we have to to explicity test for that case
+ using has_focus_window(W).
+
+
+Modifications for keyboard grabs
+================================
+
+The above algorithm ignores keyboard grabs, which also
+generate focus events, and needs to be modified somewhat
+to take keyboard grabs into effect. The basic idea
+is that for has_pointer_focus(W)/has_window_focus(W) we track
+them ignoring grabs and ungrabs, and then supplement
+that with another predicate has_focus(W) which pays
+attention to grabs and ungrabs.
+
+Modification 1:
+
+ When tracking has_pointer_focus(W), ignore all Focus
+ events with a mode of NotifyGrab or NotifyUngrab.
+
+ Note that this means that with grabs, we don't perfectly.
+ track the delivery of keyboard events ... since we think
+ we are getting events in the case where
+
+ has_pointer_focus(W) && !(G == None || G==W || G==descendant)
+
+ But the X protocol doesn't provide sufficient information
+ to do this right... example:
+
+ F=Ancestor, G=None => F=Ancestor, G=Ancestor
+
+ We stop getting events, but receive no notification.
+
+ The case of no window manager and keyboard grabs is pretty
+ rare in any case.
+
+Modification 2:
+
+ When tracking has_focus_window(W), ignore all Focus
+ events with a mode of NotifyGrab or NotifyUngrab.
+
+Modification 3: instead of calculating focus as
+
+ has_focus_window(W) || has_pointer_focus(W)
+
+ Calculate it as
+
+ has_focus(W) || has_pointer_focus(W)
+
+ where has_focus(W) is defined as:
+
+ has_focus(W): F==W || F==Descendant || G=W
+
+ Tracking has_focus(W) is done by
+
+ FocusIn: detail != NotifyInferior, mode != NotifyWhileGrabbed:
+ set has_focus
+ FocusOut: detail != NotifyInferior, mode != NotifyWhileGrabbed:
+ clear has_focus
+
+ We still need to track has_focus_window(W) for the T4/F4
+ transitions.
}
/* Handle focusing (in the case where no window manager is running */
- if (toplevel &&
- xevent->xcrossing.detail != NotifyInferior &&
- xevent->xcrossing.focus && !toplevel->has_focus_window)
+ if (toplevel && xevent->xcrossing.detail != NotifyInferior)
{
- gboolean had_focus = HAS_FOCUS (toplevel);
+ toplevel->has_pointer = TRUE;
- toplevel->has_pointer_focus = TRUE;
-
- if (HAS_FOCUS (toplevel) != had_focus)
- generate_focus_event (window, TRUE);
+ if (xevent->xcrossing.focus && !toplevel->has_focus_window)
+ {
+ gboolean had_focus = HAS_FOCUS (toplevel);
+
+ toplevel->has_pointer_focus = TRUE;
+
+ if (HAS_FOCUS (toplevel) != had_focus)
+ generate_focus_event (window, TRUE);
+ }
}
/* Tell XInput stuff about it if appropriate */
}
/* Handle focusing (in the case where no window manager is running */
- if (toplevel &&
- xevent->xcrossing.detail != NotifyInferior &&
- xevent->xcrossing.focus && !toplevel->has_focus_window)
+ if (toplevel && xevent->xcrossing.detail != NotifyInferior)
{
- gboolean had_focus = HAS_FOCUS (toplevel);
-
- toplevel->has_pointer_focus = FALSE;
-
- if (HAS_FOCUS (toplevel) != had_focus)
- generate_focus_event (window, FALSE);
+ toplevel->has_pointer = FALSE;
+
+ if (xevent->xcrossing.focus && !toplevel->has_focus_window)
+ {
+ gboolean had_focus = HAS_FOCUS (toplevel);
+
+ toplevel->has_pointer_focus = FALSE;
+
+ if (HAS_FOCUS (toplevel) != had_focus)
+ generate_focus_event (window, FALSE);
+ }
}
event->crossing.type = GDK_LEAVE_NOTIFY;
switch (xevent->xfocus.detail)
{
case NotifyAncestor:
- case NotifyNonlinear:
case NotifyVirtual:
+ /* When the focus moves from an ancestor of the window to
+ * the window or a descendent of the window, *and* the
+ * pointer is inside the window, then we were previously
+ * receiving keystroke events in the has_pointer_focus
+ * case and are now receiving them in the
+ * has_focus_window case.
+ */
+ if (toplevel->has_pointer &&
+ xevent->xfocus.mode != NotifyGrab &&
+ xevent->xfocus.mode != NotifyUngrab)
+ toplevel->has_pointer_focus = FALSE;
+
+ /* fall through */
+ case NotifyNonlinear:
case NotifyNonlinearVirtual:
- toplevel->has_focus_window = TRUE;
+ if (xevent->xfocus.mode != NotifyGrab &&
+ xevent->xfocus.mode != NotifyUngrab)
+ toplevel->has_focus_window = TRUE;
/* We pretend that the focus moves to the grab
* window, so we pay attention to NotifyGrab
* NotifyUngrab, and ignore NotifyWhileGrabbed
* but the pointer focus is ignored while a
* grab is in effect
*/
- if (xevent->xfocus.mode != NotifyGrab)
+ if (xevent->xfocus.mode != NotifyGrab &&
+ xevent->xfocus.mode != NotifyUngrab)
toplevel->has_pointer_focus = TRUE;
break;
case NotifyInferior:
switch (xevent->xfocus.detail)
{
case NotifyAncestor:
- case NotifyNonlinear:
case NotifyVirtual:
+ /* When the focus moves from the window or a descendent
+ * of the window to an ancestor of the window, *and* the
+ * pointer is inside the window, then we were previously
+ * receiving keystroke events in the has_focus_window
+ * case and are now receiving them in the
+ * has_pointer_focus case.
+ */
+ if (toplevel->has_pointer &&
+ xevent->xfocus.mode != NotifyGrab &&
+ xevent->xfocus.mode != NotifyUngrab)
+ toplevel->has_pointer_focus = TRUE;
+
+ /* fall through */
+ case NotifyNonlinear:
case NotifyNonlinearVirtual:
- toplevel->has_focus_window = FALSE;
+ if (xevent->xfocus.mode != NotifyGrab &&
+ xevent->xfocus.mode != NotifyUngrab)
+ toplevel->has_focus_window = FALSE;
if (xevent->xfocus.mode != NotifyWhileGrabbed)
toplevel->has_focus = FALSE;
break;
case NotifyPointer:
- if (xevent->xfocus.mode != NotifyUngrab)
+ if (xevent->xfocus.mode != NotifyGrab &&
+ xevent->xfocus.mode != NotifyUngrab)
toplevel->has_pointer_focus = FALSE;
break;
case NotifyInferior:
*/
guint has_focus : 1;
- /* Set if !window->has_focus_window, but events are being sent to the
- * window because the pointer is in it. (Typically, no window
- * manager is running.
+ /* Set if the pointer is inside this window. (This is needed for
+ * for focus tracking)
+ */
+ guint has_pointer : 1;
+
+ /* Set if the window is a descendent of the focus window and the pointer is
+ * inside it. (This is the case where the window will receive keystroke
+ * events even window->has_focus_window is FALSE)
*/
guint has_pointer_focus : 1;